﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Practices.Unity;
using Microsoft.Practices.ObjectBuilder2;
using Lilium.Collections;
using System.Reflection;

namespace Lilium.Unity.ContainerConfiguration
{
    [AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface, AllowMultiple = true, Inherited = true)]
    public class TypeConfigurationAttribute : Attribute, IContainerConfigurator
    {
        #region public object ConstructorParamters

        private object m_ConstructorParamters;

        public object ConstructorParamters
        {
            //[System.Diagnostics.DebuggerNonUserCodeAttribute]
            get
            {
                return m_ConstructorParamters;
            }
            //[System.Diagnostics.DebuggerNonUserCodeAttribute]
            set
            {
                m_ConstructorParamters = value;
            }
        }

        #endregion

        #region public Type LifetimeManager

        private Type m_LifetimeManager;

        public Type LifetimeManager
        {
            get { return m_LifetimeManager; }
            set { m_LifetimeManager = value; }
        }

        #endregion

        #region public Type MapTo

        private Type m_MapTo;

        public Type MapTo
        {
            get { return m_MapTo; }
            set { m_MapTo = value; }
        }

        #endregion

        #region public Type TypeToConfigure

        private Type m_TypeToConfigure;

        public Type TypeToConfigure
        {
            get
            {
                return m_TypeToConfigure;
            }
            set
            {
                m_TypeToConfigure = value;
            }
        }

        #endregion

        #region IBuildConfigurator Members

        public void ConfigureContainer(IUnityContainer container, NamedTypeBuildKey buildKey)
        {
            if (container == null) throw new ArgumentNullException("container");
            if (buildKey == null) throw new ArgumentNullException("buildKey");

            var from = TypeToConfigure ?? buildKey.Type;
            var to = from ?? MapTo;

            container.RegisterType(from, to, buildKey.Name, GetLifetimeManager(), GetInjectionMembers(to).ToArray());
        }

        #endregion

        private IEnumerable<InjectionMember> GetInjectionMembers(Type type)
        {
            if (type == null) throw new ArgumentNullException("type");

            if (ConstructorParamters != null)
            {
                var paramterValues = ConstructorParamters as object[];
                
                if (paramterValues == null)
                {
                    var paramters = ConstructorParamters.ToDictionaty();

                    paramterValues = (from constructor in type.GetConstructors()
                                      let matched = MatchParameters(constructor.GetParameters(), paramters)
                                      where matched != null
                                      select matched).Single(
                                        () => new InvalidOperationException("Matching constructor not found."),
                                        () => new InvalidOperationException("Ambiguous constructor match."));
                }

                yield return new InjectionConstructor(paramterValues);
            }
        }

        private LifetimeManager GetLifetimeManager()
        {
            if (LifetimeManager != null)
                return (LifetimeManager)Activator.CreateInstance(LifetimeManager);
            else
                return new TransientLifetimeManager();
        }

        private static object[] MatchParameters(ParameterInfo[] parameters, IDictionary<string, object> values)
        {
            if (parameters == null) throw new ArgumentNullException("parameters");
            if (values == null) throw new ArgumentNullException("values");

            if (parameters.Length == values.Count)
            {
                if (parameters.Length == 0)
                    return new object[0];
                else
                    // связывание пока не реализовано.
                    throw new NotImplementedException("private static object[] MatchParameters(ParameterInfo[] parameters, IDictionary<string, object> values)");
            }
            else
                return null;
        }
    }
}
